package tech.mhuang.pacebox.springboot.payment.wechat;

import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import tech.mhuang.pacebox.core.exception.BusinessException;
import tech.mhuang.pacebox.core.util.StringUtil;
import tech.mhuang.pacebox.springboot.core.okhttp.OkHttpDataType;
import tech.mhuang.pacebox.springboot.core.okhttp.OkhttpClientUtil;
import tech.mhuang.pacebox.springboot.payment.dto.WechatPayDTO;
import tech.mhuang.pacebox.springboot.payment.dto.WechatRefundDTO;
import tech.mhuang.pacebox.springboot.payment.wechat.util.BigDecimalUtil;
import tech.mhuang.pacebox.springboot.payment.wechat.util.PayCommonUtil;
import tech.mhuang.pacebox.springboot.payment.wechat.util.WechatConfigUtil;
import tech.mhuang.pacebox.springboot.payment.wechat.util.XMLUtil;
import tech.mhuang.pacebox.springboot.protocol.Result;

import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 微信支付服务
 *
 * @author mhuang
 * @since 1.0.0
 */
@Slf4j
public class WechatPayServer {

    private final static String APP_MODE = "APP";
    /**
     * 小程序、微信H5
     */
    private final static String JSAPI_MODE = "JSAPI";
    private final static List<String> SUPPORT_MODE_LIST = Stream.of(APP_MODE, JSAPI_MODE).collect(Collectors.toList());

    /**
     * 微信支付
     *
     * @param dto 支付的DTO
     * @return 返回支付实体
     * @throws Exception 异常
     */
    public static Map<?, ?> payment(WechatPayDTO dto) throws Exception {
        SortedMap<Object, Object> packageParams = createCommonParmas(dto.getAppId(), dto.getMchId());
        if (SUPPORT_MODE_LIST.contains(dto.getMode().toUpperCase())) {
            if (APP_MODE.equalsIgnoreCase(dto.getMode())) {
                return createAppPay(packageParams, dto.getTradeNo(), dto.getSubject(), dto.getAmount(), dto.getApiKey(), dto.getIp(), dto.getNotifyUrl(), dto.getProxyIp(), dto.getProxyPort());
            } else if (JSAPI_MODE.equalsIgnoreCase(dto.getMode())) {
                return createJsApiPay(packageParams, dto.getTradeNo(), dto.getSubject(), dto.getAmount(), dto.getApiKey(), dto.getIp(), dto.getNotifyUrl(), dto.getProxyIp(), dto.getProxyPort(), dto.getOpenId());
            }
        }
        throw new BusinessException(500, "暂不支持支付" + dto.getMode() + "方式");
    }


    /**
     * 微信退款
     *
     * @param dto 退款的实体
     * @return 退款结果
     * @throws Exception 异常
     */
    public static Map<?, ?> refundOrder(WechatRefundDTO dto) throws Exception {
        SortedMap<Object, Object> packageParams = createCommonParmas(dto.getAppId(), dto.getMchId());
        packageParams.put("out_trade_no", dto.getTradeNo());
        // 商户订单号
        packageParams.put("out_refund_no", dto.getOutRefundNo());
        // 商户退款单号
        packageParams.put("total_fee", dto.getTotalFee());
        // 订单金额
        packageParams.put("refund_fee", dto.getRefundFee());
        // 退款金额
        if (StringUtil.isNotBlank(dto.getFeeType())) {
            packageParams.put("refund_fee_type", dto.getFeeType());
            // 退款币种
        }
        if (StringUtil.isNotBlank(dto.getRefundDesc())) {
            packageParams.put("refund_desc", dto.getRefundDesc());
            // 退款原因
        }
        if (StringUtil.isNotBlank(dto.getNotifyUrl())) {
            packageParams.put("notify_url", dto.getNotifyUrl());
            // 退款结果通知url
        }

        //密匙
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, dto.getApiKey());
        // 签名
        packageParams.put("sign", sign);
        String requestXml = PayCommonUtil.getRequestXml(packageParams);
        OkHttpClient okHttpClient = OkhttpClientUtil.genOkHttpClient(dto.getProxyHost(), dto.getProxyPort(), dto.getCertPath(), dto.getMchId()).build();
        Request request = new Request.Builder().url(WechatConfigUtil.REFUND_URL)
                .post(RequestBody.create(OkHttpDataType.XML_DATA_TYPE, requestXml))
                .build();
        Response response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            return XMLUtil.doXmlParse(response.body().string());
        } else {
            throw new BusinessException(Result.SYS_FAILD, response.message());
        }
    }

    public static SortedMap<Object, Object> createCommonParmas(String appId, String mchId) {
        SortedMap<Object, Object> packageParams = new TreeMap<>();
        WechatConfigUtil.commonParams(packageParams, appId, mchId);
        return packageParams;
    }

    public static Map<?, ?> createAppPay(SortedMap<Object, Object> packageParams,
                                         String tradeNo, String subject, String totalFee, String apiKey,
                                         String ip, String notifyUrl) throws Exception {
        return createAppPay(packageParams, tradeNo, subject, totalFee, apiKey, ip, notifyUrl, null, 0);
    }

    public static Map<?, ?> createAppPay(SortedMap<Object, Object> packageParams,
                                         String tradeNo, String subject, String totalFee, String apiKey,
                                         String ip, String notifyUrl, String proxyIp, int proxyPort) throws Exception {
        return createBasePay(packageParams, tradeNo, subject, totalFee, apiKey, ip, notifyUrl, proxyIp, proxyPort, APP_MODE, null);
    }

    public static Map<?, ?> createJsApiPay(SortedMap<Object, Object> packageParams,
                                           String tradeNo, String subject, String totalFee, String apiKey,
                                           String ip, String notifyUrl, String openid) throws Exception {
        return createJsApiPay(packageParams, tradeNo, subject, totalFee, apiKey, ip, notifyUrl, null, 0, openid);
    }

    public static Map<?, ?> createJsApiPay(SortedMap<Object, Object> packageParams,
                                           String tradeNo, String subject, String totalFee, String apiKey,
                                           String ip, String notifyUrl, String proxyIp, int proxyPort, String openid) throws Exception {
        // 账号信息

        return createBasePay(packageParams, tradeNo, subject, totalFee, apiKey, ip, notifyUrl, proxyIp, proxyPort, JSAPI_MODE, openid);
    }

    public static Map<?, ?> createBasePay(SortedMap<Object, Object> packageParams,
                                          String tradeNo, String subject, String totalFee, String apiKey,
                                          String ip, String notifyUrl, String proxyIp, int proxyPort, String appMode, String openid) throws Exception {
        // 商品描述
        packageParams.put("body", subject);
        // 商户订单号
        packageParams.put("out_trade_no", tradeNo);
        totalFee = BigDecimalUtil.subZeroAndDot(totalFee);
        // 总金额
        packageParams.put("total_fee", totalFee);
        //H5支付要求商户在统一下单接口中上传用户真实ip地址 spbill_create_ip
        // 发起人IP地址
        packageParams.put("spbill_create_ip", ip);
        // 回调地址
        packageParams.put("notify_url", notifyUrl);
        // 交易类型
        packageParams.put("trade_type", appMode);
        packageParams.put("nonce_str", PayCommonUtil.createNoncestr());
        if (JSAPI_MODE.equalsIgnoreCase(appMode)) {
            packageParams.put("openid", openid);
        }
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, apiKey);
        //密匙
        packageParams.put("sign", sign);

        log.info("微信支付打印的数据是:{}", packageParams);
        // 签名
        String requestXml = PayCommonUtil.getRequestXml(packageParams);
        OkHttpClient okHttpClient = OkhttpClientUtil.genOkHttpClient(proxyIp, proxyPort).build();
        Request request = new Request.Builder().url(WechatConfigUtil.UNIFIED_ORDER_URL)
                .post(RequestBody.create(OkHttpDataType.XML_DATA_TYPE, requestXml))
                .build();
        Response response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful() || StringUtil.equals(response.message(), "OK")) {
            return XMLUtil.doXmlParse(response.body().string());
        } else {
            throw new BusinessException(500, response.message());
        }
    }
}